package com.cmcc.pcap.packet.capture;

import com.cmcc.pcap.packet.sctp.SctpDispatcher;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.cmcc.pcap.packet.tcp.TcpDispatcher;
import com.cmcc.pcap.packet.udp.UdpDispatcher;
import com.cmcc.pcap.packet.util.CommonUtils;
import org.pcap4j.core.PacketListener;
import org.pcap4j.packet.*;
import org.pcap4j.packet.namednumber.DataLinkType;
import org.pcap4j.packet.namednumber.IpVersion;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.sql.Timestamp;
import java.util.Arrays;


public class PacketHandler implements PacketListener, HandlerControl {
    private static Logger logger = LogManager.getLogger(PacketHandler.class);
    TcpDispatcher tcpDispatcher;
    UdpDispatcher udpDispatcher;
    SctpDispatcher sctpDispatcher;
    JCaptures jCaptures;
    Timestamp timestamp;
    private String taskId;

    public String getTaskId() {
        return taskId;
    }

    public void setTaskId(String taskId) {
        this.taskId = taskId;
    }

    public PacketHandler(JCaptures jCaptures, String taskId) {
        this.jCaptures = jCaptures;
        this.taskId = taskId;
        //根据filter配置启动不同的xxxDispatcher
        if ("udp".equalsIgnoreCase(this.jCaptures.firstFilter)) {
            this.udpDispatcher = new UdpDispatcher(jCaptures,taskId);
        } else if ("tcp".equalsIgnoreCase(this.jCaptures.firstFilter)) {
            this.tcpDispatcher = new TcpDispatcher(jCaptures,taskId);
        } else if ("sctp".equalsIgnoreCase(this.jCaptures.firstFilter)){
            this.sctpDispatcher = new SctpDispatcher(jCaptures,taskId);
        } else {
            this.tcpDispatcher = new TcpDispatcher(jCaptures,taskId);
            this.udpDispatcher = new UdpDispatcher(jCaptures,taskId);
            this.sctpDispatcher = new SctpDispatcher(jCaptures,taskId);
        }

    }

    //允许用户设置自定义TcpDispatcher？？？
//	public void setTcpDispatcher(TcpDispatcher tcpDispatcher){
//		this.tcpDispatcher = tcpDispatcher;
//	}


    /**
     * packet.header.length = tcpPacket.header.length:TCP最常见值是 54(14+20+20),DUP最常见42(14+20+8)
     * packet.data.length = tcpPacket.data.length:
     */
    @Override
    public void gotPacket(Packet packet) {
        try {
        	timestamp = jCaptures.captor.getTimestamp();
            jCaptures.timestamp = timestamp;
            logger.debug(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
            
        	/**
			 * LinkType类型如下:
			 * value: name
			 * 1    : Ethernet
			 * 6    : Token Ring
			 * 9    : PPP
			 * 10   : FDDI
			 * 101  : RAW IP (no linktype)
			 * 105  : Wireless
			 * 113  : Linux cooked-mode capture
			 * 127  : Radiotap
			 * 143  : DOCSIS
			 * 50   : PPP over serial with HDLC encapsulation
			 */
			DataLinkType linktype = jCaptures.captor.getDlt();
			
			/**LinkType:RAW,
			 * Raw IP; the packet begins with an IPv4 or IPv6 header, 
			 * with the "version" field of the header indicating 
			 * whether it's an IPv4 or IPv6 header.
			 */
			if(linktype.compareTo(DataLinkType.RAW) == 0) {
				logger.debug("LinkType:",linktype.valueAsString());
				IpPacket ipPacket = packet.get(IpPacket.class);
				IpVersion ip = ipPacket.getHeader().getVersion();
				if(ip.compareTo(IpVersion.IPV4) == 0) {
					IpPacket ipv4Packet = ipPacket.get(IpV4Packet.class);;
					this.dealIPPacket(ipv4Packet);
				}
				if(ip.compareTo(IpVersion.IPV6) == 0) {
					IpPacket ipv6Packet = ipPacket.get(IpV6Packet.class);
					this.dealIPPacket(ipv6Packet);
				}

			}
			
			/**LinkType:Ethernet
			 * IEEE 802.3 Ethernet (10Mb, 100Mb, 1000Mb, and up); 
			 * the 10MB in the DLT_ name is historical.
			 */
			else if(linktype.compareTo(DataLinkType.EN10MB) == 0) {
				logger.debug("LinkType:",linktype.valueAsString());
				EthernetPacket ethP = packet.get(EthernetPacket.class);
                logger.debug(ethP.toString());
                logger.debug("packet.header.length : " + packet.getHeader().length());
                logger.debug("packet.data.length : " + packet.getRawData().length);

                /**
                 * ETHERTYPE_PUP = 512;
                 ETHERTYPE_IP = 2048;
                 ETHERTYPE_ARP = 2054;
                 ETHERTYPE_REVARP = -32715;
                 ETHERTYPE_VLAN = -32512;
                 ETHERTYPE_IPV6 = -31011;
                 ETHERTYPE_LOOPBACK = -28672;
                 */
                switch (ethP.getHeader().getType().value()) {
                    case 2048:
                    	/*带有GTP协议的数据包都走ip入口，因为GTP协议是承载在IP协议之上的（中间还有一层UDP）*/
                        logger.debug("ipv4 packet");
                        IpPacket ipv4Packet = packet.get(IpV4Packet.class);
                        this.dealIPPacket(ipv4Packet);
                        break;
                    case -31011:
                        logger.debug("ipv6 packet");
                        IpPacket ipv6Packet = packet.get(IpV6Packet.class);
                        this.dealIPPacket(ipv6Packet);
                        break;
                    case 2054:
                        logger.debug("arp packet do nothing");
                        break;
                    case -32512:
                        logger.debug("vlan packet");
                        this.dealIPPacket(packet.get(IpPacket.class));
                        break;
                    default:
                        logger.error("do not support enthernet type :" + ethP.getHeader().getType());
                        break;
                }

			}
			
			/**LinkType:Linux cooked-mode capture
			 * 
			 */
			else if(linktype.compareTo(DataLinkType.LINUX_SLL) == 0) {
				logger.debug("LinkType:",linktype.valueAsString());
				LinuxSllPacket sll = packet.get(LinuxSllPacket.class);
                logger.debug(sll.toString());
                logger.debug("packet.header.length : " + packet.getHeader().length());
                logger.debug("packet.data.length : " + packet.getRawData().length);

                /**
                 * ETHERTYPE_PUP = 512;
                 ETHERTYPE_IP = 2048;
                 ETHERTYPE_ARP = 2054;
                 ETHERTYPE_REVARP = -32715;
                 ETHERTYPE_VLAN = -32512;
                 ETHERTYPE_IPV6 = -31011;
                 ETHERTYPE_LOOPBACK = -28672;
                 */
                switch (sll.getHeader().getProtocol().value()) {
                    case 2048:
                        logger.debug("ipv4 packet");
                        IpPacket ipv4Packet = packet.get(IpV4Packet.class);
                        this.dealIPPacket(ipv4Packet);
                        break;
                    case -31011:
                        logger.debug("ipv6 packet");
                        IpPacket ipv6Packet = packet.get(IpV6Packet.class);
                        this.dealIPPacket(ipv6Packet);
                        break;
                    case 2054:
                        logger.debug("arp packet do nothing");
                        break;
                    case -32512:
                        logger.debug("vlan packet");
                        this.dealIPPacket(packet.get(IpPacket.class));
                        break;
                    default:
                        logger.error("do not support enthernet type :" + sll.getHeader().getProtocol());
                        break;
                }

			}
			
			/**LinkType:others, not support
			 * 其他LinkType暂不支持
			 */
			else {
				logger.error("Not Support this Pcap Format : " + linktype.valueAsString());
			}
			
			
            
			/*
            if(packet.getHeader().length() == 16){
                //通过SLL解析
                LinuxSllPacket sll = packet.get(LinuxSllPacket.class);
                logger.debug(sll.toString());
                logger.debug("packet.header.length : " + packet.getHeader().length());
                logger.debug("packet.data.length : " + packet.getRawData().length);

                /**
                 * ETHERTYPE_PUP = 512;
                 ETHERTYPE_IP = 2048;
                 ETHERTYPE_ARP = 2054;
                 ETHERTYPE_REVARP = -32715;
                 ETHERTYPE_VLAN = -32512;
                 ETHERTYPE_IPV6 = -31011;
                 ETHERTYPE_LOOPBACK = -28672;
                 *
                switch (sll.getHeader().getProtocol().value()) {
                    case 2048:
                        logger.debug("ipv4 packet");
                        IpPacket ipv4Packet = packet.get(IpV4Packet.class);
                        this.dealIPPacket(ipv4Packet);
                        break;
                    case -31011:
                        logger.debug("ipv6 packet");
                        IpPacket ipv6Packet = packet.get(IpV6Packet.class);
                        this.dealIPPacket(ipv6Packet);
                        break;
                    case 2054:
                        logger.debug("arp packet do nothing");
                        break;
                    case -32512:
                        logger.debug("vlan packet");
                        this.dealIPPacket(packet.get(IpPacket.class));
                        break;
                    default:
                        logger.error("do not support enthernet type :" + sll.getHeader().getProtocol());
                        break;
                }
            }else if(packet.getHeader().length() == 14){
                //header.length == 14   Ethernet
                EthernetPacket ethP = packet.get(EthernetPacket.class);
                logger.debug(ethP.toString());
                logger.debug("packet.header.length : " + packet.getHeader().length());
                logger.debug("packet.data.length : " + packet.getRawData().length);

                /**
                 * ETHERTYPE_PUP = 512;
                 ETHERTYPE_IP = 2048;
                 ETHERTYPE_ARP = 2054;
                 ETHERTYPE_REVARP = -32715;
                 ETHERTYPE_VLAN = -32512;
                 ETHERTYPE_IPV6 = -31011;
                 ETHERTYPE_LOOPBACK = -28672;
                 *
                switch (ethP.getHeader().getType().value()) {
                    case 2048:
                    	/*带有GTP协议的数据包都走ip入口，因为GTP协议是承载在IP协议之上的（中间还有一层UDP）*
                        logger.debug("ipv4 packet");
                        IpPacket ipv4Packet = packet.get(IpV4Packet.class);
                        this.dealIPPacket(ipv4Packet);
                        break;
                    case -31011:
                        logger.debug("ipv6 packet");
                        IpPacket ipv6Packet = packet.get(IpV6Packet.class);
                        this.dealIPPacket(ipv6Packet);
                        break;
                    case 2054:
                        logger.debug("arp packet do nothing");
                        break;
                    case -32512:
                        logger.debug("vlan packet");
                        this.dealIPPacket(packet.get(IpPacket.class));
                        break;
                    default:
                        logger.error("do not support enthernet type :" + ethP.getHeader().getType());
                        break;
                }
            }else {
            	logger.error("not a standard pcap format : " + packet.getRawData());
            }
            */
        } catch (Exception e) {
            logger.error(e.getCause(), e);
        }
    }

    public void dealIPPacket(IpPacket ipPacket) {

        String src_ip;
        int src_port;
        String dst_ip;
        int dst_port;
        short protocol;

        /**
         * IPPROTO_ICMP = 1;
         IPPROTO_IGMP = 2;
         IPPROTO_IP = 4;   
         IPPROTO_TCP = 6;  support
         IPPROTO_UDP = 17;  support
         IPPROTO_IPv6 = 41;
         IPPROTO_HOPOPT = 0;
         IPPROTO_IPv6_Route = 43;
         IPPROTO_IPv6_Frag = 44;
         IPPROTO_IPv6_ICMP = 58;
         IPPROTO_IPv6_NoNxt = 59;
         IPPROTO_IPv6_Opts = 60;
         */
        switch (ipPacket.getHeader().getProtocol().value()) {
            case 17:

                UdpPacket udpPacket = ipPacket.get(UdpPacket.class);

                /**
                 * 五元组(源IP、源端口、目的IP、目的端口、协议。代码到这里已经确定传输层协议是UDP，应用层协议需要另行分析)
                 * TODO 需要一个用于分析应用层协议是何种协议的独立类或方法
                 */
                src_ip = ipPacket.getHeader().getSrcAddr().getHostAddress();
                src_port = udpPacket.getHeader().getSrcPort().valueAsInt();
                dst_ip = ipPacket.getHeader().getDstAddr().getHostAddress();
                dst_port = udpPacket.getHeader().getDstPort().valueAsInt();
                protocol = ipPacket.getHeader().getProtocol().value();
                logger.debug("udp quintet :<" + src_ip + "," + src_port + "," + dst_ip + "," + dst_port + "," + protocol + ">");
                //logger.debug(udpPacket.toString());
                //logger.debug(udpPacket.data); //TODO 处理应用层协议

                /**
                 * 给GTP去头,将搭载的数据包重新递归处理
                 *
                 * GTP只承载与UDP上,并且GTP-U专用端口为2152
                 * 根据端口识别是否为GTP协议,然后获取其数据部分的byte数组(去头)
                 * 通过反射的方式生成IpPacket对象,递归本方法重新识别
                 */
                if (src_port == 2152) {
                    logger.debug("GTP-U packet");
                    GtpV1Packet gtpV1Packet = ipPacket.get(GtpV1Packet.class);
                    if (gtpV1Packet == null) {
                        break;
                    }
                    byte[] rawData = gtpV1Packet.getPayload().getRawData();//得到payload的二进制表示

                    //反射
                    try {
                    	if(rawData != null || rawData.length != 0) {
                    		//截取IP头的前4个bit，得到ip层的version字段
                        	byte data =  rawData[0];
                        	String version = Integer.toBinaryString(data & 0xFF);
                        	version = version.substring(0,version.length()-4);
                        	logger.debug("version:", version);
                        	
                        	Class<?> ipProtocolPacket; 
                            Constructor<?> constructor = null;
                        	//根据不同的IP协议处理不同的IP报文
                        	if(version.equals("100")) {
                        		//ipv4的包
                        		ipProtocolPacket = Class.forName("org.pcap4j.packet.IpV4Packet");
                                constructor = ipProtocolPacket.getDeclaredConstructor(byte[].class, int.class, int.class);
                                constructor.setAccessible(true);
                                Object instance = constructor.newInstance(rawData, 0, rawData.length);
                                IpPacket gtpDataPacket = (IpPacket) instance;
                                this.dealIPPacket(gtpDataPacket);
                        	}
                        	if(version.equals("110")) {
                        		//ipv6的包
                        		ipProtocolPacket = Class.forName("org.pcap4j.packet.IpV6Packet");
                                constructor = ipProtocolPacket.getDeclaredConstructor(byte[].class, int.class, int.class);
                                constructor.setAccessible(true);
                                Object instance = constructor.newInstance(rawData, 0, rawData.length);
                                IpPacket gtpDataPacket = (IpPacket) instance;
                                this.dealIPPacket(gtpDataPacket);
                        	}
                        
                            /*
                            if (jCaptures.filter.contains("tcp") && gtpDataPacket != null) {
                                //如通过过滤条件没有开启tcp缓存,则在此自动开启tcp缓存.
                                if (this.tcpDispatcher == null) {
                                    this.tcpDispatcher = new TcpDispatcher(jCaptures,this.taskId);
                                    tcpDispatcher.dispatcherTcpPacket(gtpDataPacket);
                                } else {
                                    tcpDispatcher.dispatcherTcpPacket(gtpDataPacket);
                                }
                            } */   

                                
                        	
                        }
                    	
                    } catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
                        logger.error(e);
                    }
                    break;
                }
                
                /**
                 * Vxlan通过UDP端口号4789转发报文
                 * * 协议栈：Ethernet头|[IP头|UDP头|VLAN头]|正常2层协议（头+数据）
                 */
                if(dst_port == 4789) {
//                	System.out.println("vxlan packet");
                	logger.debug("vxlan packet");

                	
                	/**
                	 * vxlan报文
                	 * 头部：8 bytes
                	 * 数据部分：标准Ethernet数据包
                	 * 构造Ethernet报文：使用Ehthernet的newPacket方法，将数据部分格式化为Ethernet数据包
                	 */
                	byte[] vxlanRawdata = udpPacket.getPayload().getRawData();//获取udp协议数据部分二进制码流
                	int offset = 8;// offset为rawdata中Ethernet包开始的位置
                	int length = vxlanRawdata.length - offset;//length为整个Ethernet包的长度
                	EthernetPacket ethP = null;
					try {
						ethP = EthernetPacket.newPacket(vxlanRawdata, offset, length);
					} catch (IllegalRawDataException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
                	
					
                    logger.debug(ethP.toString());
                    logger.debug("packet.header.length : " + ethP.getHeader().length());
                    logger.debug("packet.data.length : " + ethP.getRawData().length);

                    /**
                     * ETHERTYPE_PUP = 512;
                     ETHERTYPE_IP = 2048;
                     ETHERTYPE_ARP = 2054;
                     ETHERTYPE_REVARP = -32715;
                     ETHERTYPE_VLAN = -32512;
                     ETHERTYPE_IPV6 = -31011;
                     ETHERTYPE_LOOPBACK = -28672;
                     */
                    switch (ethP.getHeader().getType().value()) {
                        case 2048:
                        	/*带有GTP协议的数据包都走ip入口，因为GTP协议是承载在IP协议之上的（中间还有一层UDP）*/
                            logger.debug("ipv4 packet");
                            IpPacket ipv4Packet = ethP.get(IpV4Packet.class);
                            this.dealIPPacket(ipv4Packet);
                            break;
                        case -31011:
                            logger.debug("ipv6 packet");
                            IpPacket ipv6Packet = ethP.get(IpV6Packet.class);
                            this.dealIPPacket(ipv6Packet);
                            break;
                        case 2054:
                            logger.debug("arp packet do nothing");
                            break;
                        case -32512:
                            logger.debug("vlan packet");
                            this.dealIPPacket(ethP.get(IpPacket.class));
                            break;
                        default:
                            logger.error("do not support enthernet type :" + ethP.getHeader().getType());
                            break;
                    }
                    
                	break;
                }

                /**
                 * MF=0,OFFSET=0  不是分片
                 MF=1,OFFSET=0 第一个分片
                 MF=1,OFFSET=X 中间某个分片
                 MF=0,OFFSET=X 最后一个分片
                 */
                logger.debug("UDP packet");
                if (!jCaptures.firstFilter.contains("udp")){
                    break;
                }

                this.udpDispatcher.dispatcherUdpPacket(ipPacket);
                break;
            case 6:
                logger.debug("TCP packet");
                if (!jCaptures.firstFilter.contains("tcp")){
                    break;
                }
                TcpPacket tcpPacket = ipPacket.get(TcpPacket.class);

                /**
                 * 五元组(源IP、源端口、目的IP、目的端口、协议。代码到这里已经确定传输层协议是TCP，应用层协议需要另行分析)
                 * TODO 需要一个用于分析应用层协议是何种协议的独立类或方法
                 */
                src_ip = ipPacket.getHeader().getSrcAddr().getHostAddress();
                src_port = tcpPacket.getHeader().getSrcPort().valueAsInt();
                dst_ip = ipPacket.getHeader().getDstAddr().getHostAddress();
                dst_port = tcpPacket.getHeader().getDstPort().valueAsInt();
                protocol = ipPacket.getHeader().getProtocol().value();
                logger.debug("tcp quintet :<" + src_ip + "," + src_port + "," + dst_ip + "," + dst_port + "," + protocol + ">");

                tcpDispatcher.dispatcherTcpPacket(ipPacket);

                break;

            case (byte) 132:
                logger.debug("SCTP packet");
                if (!jCaptures.firstFilter.contains("sctp")){
                    break;
                }

                SctpPacket sctpPacket = ipPacket.get(SctpPacket.class);

                src_ip = ipPacket.getHeader().getSrcAddr().getHostAddress();
                src_port = sctpPacket.getHeader().getSrcPort().valueAsInt();
                dst_ip = ipPacket.getHeader().getDstAddr().getHostAddress();
                dst_port = sctpPacket.getHeader().getDstPort().valueAsInt();
                protocol = ipPacket.getHeader().getProtocol().value();
                logger.debug("tcp quintet :<" + src_ip + "," + src_port + "," + dst_ip + "," + dst_port + "," + protocol + ">");

                sctpDispatcher.dispatcherSctpPacket(ipPacket);

                break;
            default:
                logger.error("do not support ip protocol :" + ipPacket.getHeader().getProtocol().value());
                logger.error("the data is  :" + CommonUtils.bytesToHexString(ipPacket.getRawData()));
                break;

        }
    }

    /**
     * 如果有多个其他dispatcher也要依次stop
     */
    @Override
    public void stop() {
        logger.info("[ stoping PacketHandler ]");
        //logger.info("stop TcpDispatcher");
        if (this.tcpDispatcher != null)
            this.tcpDispatcher.stop();

        if (this.udpDispatcher != null)
            this.udpDispatcher.stop();

        if (this.sctpDispatcher != null){
            this.sctpDispatcher.stop();
        }
        logger.info("[ stoped PacketHandler ]");
    }

}
